# pip install gevent-websocket
# pip install flask-socketio

import cv2
import threading
from flask import Flask, render_template, Response, request, jsonify    
import os
import socket
import json
import threading
import queue
from PIL import Image
import time
import configparser 


# for optimization
from flask_socketio import SocketIO, emit
import base64

# Disable OpenCV verbose logging
cv2.setLogLevel(0)

# app = Flask(__name__)

# async_mode
# The asynchronous model to use.
#  See the Deployment section in the documentation for a description of the available options.
#  Valid async modes are threading, eventlet, gevent and gevent_uwsgi. 
# If this argument is not given, eventlet is tried first, 
# then gevent_uwsgi, then gevent, and finally threading. 
# The first async mode that has all its dependencies installed is then one that is chosen.

app = Flask(__name__)
socketio = SocketIO(app, 
                    ping_timeout=1, 
                    ping_interval=1, 
                    max_http_buffer_size=50000000,
                    async_mode='threading',
                    cors_allow_origins='*',
                    )  # 初始化SocketIO

# socketio = SocketIO(app)  # 初始化SocketIO


sock = None
SockBufLen = 8*1024

# (640, 480),
# # (800, 600),
# (1024, 768),
# (1280, 720),
# (1280, 1024),
# # (1600, 1200),
# # (1920, 1080),
# # (2560, 1440),
# # (3840, 2160)

FW = 1024
FH = 768


#########################################################################################
# COMMON FUNCTIONS
#########################################################################################

def read_item_from_config_ini(section, item):
    cf = configparser.ConfigParser()
    cf.read('./Config.ini', encoding='gbk')
    return cf.get(section, item)

def write_item_to_config_ini(section, item, value):
    cf = configparser.ConfigParser()
    cf.read('./Config.ini', encoding='gbk')
    cf.set(section, item, value)
    with open('./Config.ini', 'w') as configfile:
        cf.write(configfile)
    return 'save to Config.ini, ok.'


def get_current_available_camera_list():
    index = read_item_from_config_ini('camera', 'index')
    print(index)
    if index == '':
        return []
    else:
        camera_index = []
        for i in index.split(','):
            camera_index.append(int(i))
        return camera_index
    


# 保存摄像头流对象
camera_streams = {}

camera_index_list = read_item_from_config_ini('camera', 'index').split(',')

#########################################################################################
# CAMERA
#########################################################################################







#########################################################################################
# SETTINGS
#########################################################################################

@app.route('/')
@app.route('/Settings')
def Settings():
    print("Settings is index page ...")
    return render_template('settings.html')


@app.route('/btn_connect', methods=['POST'])
def btn_connect():
    ip_val = request.form["ip_val"]
    port_val = int(request.form["port_val"])
    try:
        # Create a TCP socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_address = (ip_val, port_val)
        # Connect to the server
        sock.connect(server_address)
        # clear the recv buffer
        sock.recv(SockBufLen)
        return "连接成功"
    except Exception as e:
        # col1.write(f"连接失败: {e}")
        sock = None
        return f"连接失败: {e}"
       

@app.route('/btn_connect_status', methods=['POST'])
def btn_connect_status():
    if sock is None:
        return "未连接"
    else:
        return "已连接"



@app.route('/btn_cam_info', methods=['POST'])
def btn_cam_info():
    # 常见的分辨率列表
    common_resolutions = [
        (640, 480),
        (800, 600),
        (1024, 768),
        # (1280, 720),
        # (1280, 1024),
        # (1600, 1200),
        # (1920, 1080),
        # (2560, 1440),
        # (3840, 2160)
    ]
    supported_resolutions = []

    available_cameras = []
    CAMERA_COUNT = 8
    for index in range(CAMERA_COUNT):
        print(f"Checking camera {index} ...")
        cap = cv2.VideoCapture(index)
        if cap.isOpened():
            available_cameras.append(index)
            cap.release()  # 释放摄像头
        

    if len(available_cameras) == 0:
        write_item_to_config_ini('camera', 'index', '')
    else:    
        write_item_to_config_ini('camera', 'index', str(available_cameras).replace('[','').replace(']',''))

    return str(available_cameras) 

    # supported_resolutions_all = []
    # for camera_index in available_cameras:
    #     cap = cv2.VideoCapture(camera_index)
    #     if not cap.isOpened():
    #         print("Cannot open camera")
    #         continue

    
    #     for width, height in common_resolutions:
    #         # 尝试设置分辨率
    #         cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    #         cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

    #         # 获取设置后的分辨率
    #         actual_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    #         actual_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)

    #         # 检查设置是否成功
    #         if int(actual_width) == width and int(actual_height) == height:
    #             supported_resolutions.append(("CAM"+ str(camera_index), width, height))

    #     supported_resolutions_all.append(supported_resolutions)
    #     cap.release()
    # # return supported_resolutions

    # return str(available_cameras) + "|" + str(supported_resolutions_all)



@app.route('/btn_cam_info1', methods=['POST'])
def btn_cam_info1():
    cam_id = int(request.form["cam_id"])
    info = ""

    try:
        # 打开摄像头
        cap = cv2.VideoCapture(cam_id)  # index 0 表示第一个摄像头

        # 获取常见属性
        frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # 宽度
        print("current width: ", frame_width)
        frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 高度
        print("current height: ", frame_height)

        fps = cap.get(cv2.CAP_PROP_FPS)  # 帧率
        brightness = cap.get(cv2.CAP_PROP_BRIGHTNESS)  # 亮度
        contrast = cap.get(cv2.CAP_PROP_CONTRAST)  # 对比度
        saturation = cap.get(cv2.CAP_PROP_SATURATION)  # 饱和度
        hue = cap.get(cv2.CAP_PROP_HUE)  # 色调
        gain = cap.get(cv2.CAP_PROP_GAIN)  # 增益
        exposure = cap.get(cv2.CAP_PROP_EXPOSURE)  # 曝光
        fourcc = cap.get(cv2.CAP_PROP_FOURCC)  # 编码格式

        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("分辨率", f"{frame_width}x{frame_height}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("帧率", f"{fps}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("亮度", f"{brightness}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("对比度", f"{contrast}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("饱和度", f"{saturation}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("色调", f"{hue}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("增益", f"{gain}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("曝光", f"{exposure}")
        info += "<tr><td style='font-size: 12px' width='35%'>{}</td><td><span style='color:#666;font-size: 12px'>{}</span></td></tr>".format("编码格式", f"{fourcc}")

        cap.release()
        return info
    except Exception as e:
        return f"Error: {e}"



@app.route('/btn_api_debug_send', methods=['POST'])
def btn_api_debug_send():

    api_cmd = request.form["api_cmd"]
    api_params = request.form["api_params"]

    api_request = f"{api_cmd}?{api_params}"
    if sock is not None:
    # if True:
        try:
            # Send the request
            sock.send(api_request.encode())
            # SockBufLen
            rx = sock.recv(4096)  


            # # -----------------AS STRING------------------------------------------------------
            # # col1.write(rx.decode())
            # # return rx.decode()

            # -----------------AS JSON--------------------------------------------------------
            # Decode the response to a string
            response_str = rx.decode()
            # # Parse the string as JSON
            response_json = json.loads(response_str)

            formatted_data = json.dumps(response_json, indent=4, ensure_ascii=False) 

            print(formatted_data)

            return formatted_data
        
            # # Accessing JSON data as a dictionary
            # if isinstance(response_json, dict):
            #     for key, value in response_json.items():
            #         col1.write(f"{key}: {value}")
        except Exception as e:
            return( f"发送失败: {e}")
    else:
        return("发送失败: Socket 未连接")


# 捕获帧并保存为BMP图片的函数
def capture_and_save_frame(camera_id, save_path):
    print("capture:" + str(camera_id))
    print("-----capture_and_save_frame-----")
    print(save_path)

    if camera_id in camera_streams:
        print("camera_id in camera_streams")

        frame_encoded = camera_streams[camera_id]
        print("-----frame-----")
        # print(frame_encoded)

        if True:
            # 修改时间戳格式，避免使用冒号
            timestamp = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
            filename = os.path.join(save_path, f'camera_{camera_id}_{timestamp}.bmp')

            # 将 base64 编码的帧数据解码并保存为 BMP 图片
            with open(filename, "wb") as f:
                f.write(base64.b64decode(frame_encoded))
                
            print(f"Frame saved at {filename}")
            return True
    else:
        print(f"Error: Camera ID {camera_id} not found in camera_streams")
        return False



def capture_and_save_frame_all(save_path):
    print("-----capture_and_save_frame_all-----")
    print(save_path)
    ALL_PASS = True
    camera_index_list = get_current_available_camera_list()

    if len(camera_index_list) == 0:
        # return "No available cameras!"
        return False
    else:
        try:
            start_time = time.time()  # Start time
            for camera_id in camera_index_list:
                print("capture:" + str(camera_id))
                if camera_id in camera_streams:
                    print("camera_id in camera_streams")

                    frame_encoded = camera_streams[camera_id]
                    print("-----frame-----")
                    # print(frame_encoded)

                    if True:
                        # 修改时间戳格式，避免使用冒号
                        timestamp = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
                        filename = os.path.join(save_path, f'camera_{camera_id}_{timestamp}.bmp')

                        # 将 base64 编码的帧数据解码并保存为 BMP 图片
                        with open(filename, "wb") as f:
                            f.write(base64.b64decode(frame_encoded))
                            
                        print(f"Frame saved at {filename}")

            end_time = time.time()  # End time
            cost_time = end_time - start_time  # Calculate the cost time, only for test!!!
            # return f"All Images captured OK! Cost time: {cost_time} seconds"
        except:
            ALL_PASS = False

        if ALL_PASS:   
            # return f"All Images captured OK! Cost time: {cost_time} seconds"
            return True
        else:
            # return f"Failed to capture image from camera {camera_index}."
            # return f"Failed to capture all images."
            return False



@socketio.on('capture_camera')
def handle_capture_camera(data):
    camera_id = data.get('camera_id')
    # 在这里处理摄像头捕获逻辑

    camera_id = data.get('camera_id')
    save_path = './captured_frames'

    # 确保保存路径存在
    if not os.path.exists(save_path):
        os.makedirs(save_path)

    # 捕获帧并保存为BMP图片
    if capture_and_save_frame(camera_id, save_path):
        emit('response', {'status': 'success', 'camera_id': camera_id, 'save_path': save_path})
    else:
        emit('response', {'status': 'error', 'camera_id': camera_id})



@socketio.on('capture_camera_all')
def handle_capture_camera_all():
    save_path = './captured_frames'
    # 确保保存路径存在
    if not os.path.exists(save_path):
        os.makedirs(save_path)

    # 捕获帧并保存为BMP图片
    if capture_and_save_frame_all(save_path):
        emit('response', {'status': 'success'})
    else:
        emit('response', {'status': 'error'})


@app.route('/Camera')
def index():
    camera_index = get_current_available_camera_list()
    return render_template('index.html', camera_index=camera_index)


# 获取每个摄像头的帧并通过WebSocket推送给客户端

def gen_frames_ws(camera_id):
    print('gen_frames_ws: ', camera_id)

    camera = cv2.VideoCapture(camera_id)

    camera.set(cv2.CAP_PROP_FRAME_WIDTH, FW)   # 降低分辨率
    camera.set(cv2.CAP_PROP_FRAME_HEIGHT, FH)  # 降低分辨率
    camera.set(cv2.CAP_PROP_FPS, 14)  # 降低帧率

    while True:
        success, frame = camera.read()  # 捕获视频帧
        
        if success:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 将帧编码为JPEG格式
            _, buffer = cv2.imencode('.jpg', frame)
            frame_data = buffer.tobytes()

            # 将帧数据转换为base64编码以便通过WebSocket传输
            frame_encoded = base64.b64encode(frame_data).decode('utf-8')

            #kzeng add 
            camera_streams[camera_id] = frame_encoded

            # 推送帧数据到前端
            socketio.emit('video_frame', {'frame': frame_encoded, 'camera_id': camera_id})

        socketio.sleep(0.04)  # 控制推送频率以减少资源消耗 1/24 = 0.04s



# 在后台启动线程来处理摄像头的帧捕获
def start_camera_stream(camera_id):
    stream_thread = threading.Thread(target=gen_frames_ws, args=(camera_id,))
    stream_thread.start()



# WebSocket连接事件
@socketio.on('connect')
def handle_connect():
    print("Client connected")
    camera_index = get_current_available_camera_list()
    # 启动摄像头流推送
    for cam in camera_index:
        # if cam == 0: #skip camera 0,notebnook camera?
        #     continue
        print(f"Starting camera stream for camera {cam}")
        start_camera_stream(cam)



if __name__ == '__main__':
    print('start server ...')
    print('port: 5166')
    print('host: 0.0.0.0')
    socketio.run(app, host='0.0.0.0', port=5166, debug=False)
